using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;

namespace XNASubdivision
{
    class FirstPersonShooterCamera : DrawableGameComponent
    {
        public static Matrix CreatePerspectiveFieldOfViewLH(float fieldOfViewY, float aspectRatio, float znearPlane, float zfarPlane)
        {
            //This method uses the following formula to compute the returned matrix. 
            //The view space height is represented by h. It is calculated from h = cot(fieldOfViewY/2). 
            //    The view space width is represented by w. It is calculated from h = w / aspectRatio.
            //w       0       0                                              0
            //0       h       0                                              0
            //0       0       zfarPlane/(zfarPlane-znearPlane)              1
            //0       0       znearPlane*zfarPlane/(znearPlane-zfarPlane)    0


            float h = 1.0f / (float)Math.Tan(fieldOfViewY / 2);
            float w = h * aspectRatio;
            Matrix matrix = new Matrix(w, 0, 0, 0,
                                        0, h, 0, 0,
                                        0, 0, zfarPlane / (zfarPlane - znearPlane), 1,
                                        0, 0, znearPlane * zfarPlane / (znearPlane - zfarPlane), 0);
            return matrix;

        }

        public static Matrix CreateLookAtLH(Vector3 cameraPosition, Vector3 cameraTarget, Vector3 cameraUpVector)
        {
            //LookAtLH 
            //  https://msdn2.microsoft.com/en-us/library/microsoft.windowsmobile.directx.matrix.lookatlh.aspx
            //zaxis = normal(cameraTarget - cameraPosition)
            //xaxis = normal(cross(cameraUpVector, zaxis))
            //yaxis = cross(zaxis, xaxis)
            // xaxis.x           yaxis.x           zaxis.x          0
            // xaxis.y           yaxis.y           zaxis.y          0
            // xaxis.z           yaxis.z           zaxis.z          0
            //-dot(xaxis, cameraPosition)  -dot(yaxis, cameraPosition)  -dot(zaxis, cameraPosition)  1

            Vector3 zaxis = Vector3.Normalize(cameraTarget - cameraPosition);
            Vector3 xaxis = Vector3.Normalize(Vector3.Cross(cameraUpVector, zaxis));
            Vector3 yaxis = Vector3.Cross(zaxis, xaxis);
            Matrix matrix = new Matrix(xaxis.X, yaxis.X, zaxis.X, 0,
                             xaxis.Y, yaxis.Y, zaxis.Y, 0,
                             xaxis.Z, yaxis.Z, zaxis.Z, 0,
                            -Vector3.Dot(xaxis, cameraPosition), -Vector3.Dot(yaxis, cameraPosition), -Vector3.Dot(zaxis, cameraPosition), 1);

            return matrix;
        }

        private bool m_isLeftHanded = false;
        public bool IsLeftHanded
        {
            get { return m_isLeftHanded; }
            set { m_isLeftHanded = value; }
        }

        private Matrix view;
        public Matrix View
        {
            get { return view; }
        }

        private Matrix projection;
        public Matrix Projection
        {
            get { return projection; }
        }

        private Vector3 position = new Vector3(0, 1, 2);
        public Vector3 Position
        {
            get { return position; }
            set { position = value; 

            }
        }

        private float yaw;
        public float Yaw
        {
            get { return yaw; }
            set { yaw = value; }
        }

        private float pitch;
        public float Pitch
        {
            get { return pitch; }
            set
            {
                pitch = MathHelper.Clamp(value, -MathHelper.ToRadians(89), MathHelper.ToRadians(89));
            }
        }

        private float movementSpeed = 2;
        public float MovementSpeed
        {
            get { return movementSpeed; }
            set { movementSpeed = value; }
        }

        private Vector3 up;
        public Vector3 Up
        {
            get { return up; }
        }

        private Vector3 forward;
        public Vector3 Forward
        {
            get { return forward; }
        }


        public FirstPersonShooterCamera(Game game)
            : base(game)
        {
            Visible = false;
        }

        public override void Initialize()
        {
            base.Initialize();
            UpdateProjectionMatrix();
        }

        private void UpdateProjectionMatrix()
        {
            Viewport vp = GraphicsDevice.Viewport;
            if (m_isLeftHanded)
                projection = CreatePerspectiveFieldOfViewLH(MathHelper.ToRadians(45), vp.Width / (float)vp.Height, 0.01f, 1000.0f);
            else
                projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), vp.Width / (float)vp.Height, 0.01f, 1000.0f);
        }

        public override void Update(GameTime gameTime)
        {
            float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;

            HandleYawPitch(dt);
            Matrix cameraOrientation;
            if (m_isLeftHanded)
                cameraOrientation = Matrix.CreateRotationX(pitch) * Matrix.CreateRotationY(-yaw);
            else
                cameraOrientation = Matrix.CreateRotationX(pitch) * Matrix.CreateRotationY(yaw);

            Vector3 movement = HandleMovement(dt);
            Vector3.Transform(ref movement, ref cameraOrientation, out movement);
            position += movement;

            if (m_isLeftHanded)
                view = CreateLookAtLH(position, position + cameraOrientation.Forward, Vector3.Up);
            else
                view = Matrix.CreateLookAt(position, position + cameraOrientation.Forward, Vector3.Up);

            up = cameraOrientation.Up;
            forward = cameraOrientation.Forward;

            base.Update(gameTime);
        }

        private Vector3 HandleMovement(float dt)
        {
            KeyboardState keyboardState = Keyboard.GetState();


            Vector3 move = Vector3.Zero;
            if (keyboardState.IsKeyDown(Keys.Up) || keyboardState.IsKeyDown(Keys.OemComma))
            {
                move.Z -= 1;
            }
            if (keyboardState.IsKeyDown(Keys.Down) || keyboardState.IsKeyDown(Keys.O))
            {
                move.Z += 1;
            }
            if (keyboardState.IsKeyDown(Keys.Left) || keyboardState.IsKeyDown(Keys.A))
            {
                if (m_isLeftHanded)
                    move.X += 1;
                else
                    move.X -= 1;
            }
            if (keyboardState.IsKeyDown(Keys.Right) || keyboardState.IsKeyDown(Keys.E))
            {
                if (m_isLeftHanded)
                    move.X -= 1;
                else
                    move.X += 1;
            }
            if (keyboardState.IsKeyDown(Keys.P))
            {
                move.Y += 1;
            }
            if (keyboardState.IsKeyDown(Keys.U))
            {
                move.Y -= 1;
            }
            if (move.LengthSquared() != 0)
            {
                move.Normalize();
            }
            move *= movementSpeed * dt;

            return move;
        }

        MouseState lastMouseState;
        private void HandleYawPitch(float dt)
        {
            MouseState mouseState = Mouse.GetState();
            if (lastMouseState != null)
            {
                Yaw -= (mouseState.X - lastMouseState.X) * dt * 0.4f;
                Pitch -= (mouseState.Y - lastMouseState.Y) * dt * 0.4f;
            }

            lastMouseState = mouseState;
        } 


    }
}
